//-----------------------------------------------------------------------
// <copyright file="Conversation.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//-----------------------------------------------------------------------

namespace Microsoft.Live.Messenger
{
    using System;
    using System.ComponentModel;
    using Microsoft.Live.Core;

    /// <summary>
    /// Represents a conversation between two or more instant messaging addresses.
    /// </summary>
    public class Conversation : INotifyPropertyChanged, IDisposable
    {
        private readonly MessengerEntity entity;

        private readonly User user;

        private IMAddressCollection roster;
        private TypingIMAddressCollection typingAddresses;
        private MessageHistory history;

        /// <summary>
        /// Initializes a new instance of the <see cref="Conversation"/> class.
        /// </summary>
        /// <param name="User">The User who initiates the conversation.</param>
        /// <param name="address">The instant messaging address of the contact with which to converse.</param>
        protected internal Conversation(User user, IMAddress address)
        {
            if (user == null)
            {
                throw ExceptionHelper.CreateArgumentNullException("User");
            }

            if (address == null)
            {
                throw ExceptionHelper.CreateArgumentNullException("address");
            }

            this.user = user;

            this.user.ConversationFactory.Add(this);

            this.entity = MessengerEntity.CreateInstance(typeof(Conversation), user.Entity, address.Entity);
            this.AttachEvents();
        }

        internal Conversation(MessengerEntity entity, User user)
        {
            this.user = user;

            this.entity = entity;
            this.AttachEvents();
        }

        private void AttachEvents()
        {
            this.entity.AttachEvent("InviteAddressCompleted", this.OnInviteAddressCompleted);
            this.entity.AttachEvent("MessageReceived", this.OnMessageReceived);
            //this.entity.AttachEvent("PropertyChanged", this.OnPropertyChanged);
            this.entity.AttachEvent("SendMessageFailed", this.OnSendMessageFailed);
        }

        internal MessengerEntity Entity
        {
            get { return this.entity; }
        }

        /// <summary>
        /// Gets the roster (list of participants) for the conversation.
        /// </summary>
        public IMAddressCollection Roster
        {
            get
            {
                if (this.roster == null)
                {
                    this.roster = new IMAddressCollection(this.entity.GetProperty("Roster"), this.user);
                }
                return this.roster;
            }
        }

        /// <summary>
        /// Gets the addresses of the participants that are currently typing within the conversation.
        /// </summary>
        public TypingIMAddressCollection TypingAddresses
        {
            get
            {
                if (this.typingAddresses == null)
                {
                    this.typingAddresses = new TypingIMAddressCollection(this.entity.GetProperty("TypingAddresses"), this.user);
                }
                return this.typingAddresses;
            }
        }

        /// <summary>
        /// Gets the message history for the conversation.
        /// </summary>
        public MessageHistory History
        {
            get
            {
                if (this.history == null)
                {
                    this.history = new MessageHistory(this.entity.GetProperty("History"), this.user);
                }
                return this.history;
            }
        }

        /// <summary>
        /// Gets whether the conversation is closed.
        /// </summary>
        public bool Closed
        {
            get { return this.entity.GetProperty<bool>("Closed"); }
        }

        /// <summary>
        /// Gets whether multiple particpants are supported within this conversation.
        /// </summary>
        public bool SupportsMultiparty
        {
            get { return this.entity.GetProperty<bool>("SupportsMultiparty"); }
        }

        /// <summary>
        /// Notifies the conversation participants that the User has typed.
        /// </summary>
        public void NotifyUserTyped()
        {
            this.entity.Invoke("NotifyUserTyped");
        }

        public void InviteAddress(IMAddress address)
        {
            if (address == null)
            {
                throw ExceptionHelper.CreateArgumentNullException("address");
            }

            if (!this.SupportsMultiparty)
            {
                throw ExceptionHelper.CreateInvalidOperationException("Multiparty chat is not supported");
            }

            if (this.roster.Contains(address))
            {
                throw ExceptionHelper.CreateInvalidOperationException("IMAddress is already a member of the conversation's roster");
            }

            this.InviteAddress(address, null);
        }

        /// <summary>
        /// Invites the address to the conversation.
        /// </summary>
        /// <param name="address">The address to which the invitation will be sent.</param>
        /// <param name="userState">
        /// A User-defined object that is passed to the method invoked when the asynchronous operation completes.
        /// </param>
        public void InviteAddress(IMAddress address, object userState)
        {
            this.entity.Invoke("InviteAddress", address.Entity, userState);
        }

        public void SendMessage(Message message)
        {
            this.SendMessage(message, null);
        }

        /// <summary>
        /// Sends a message to all participants in the conversation.
        /// </summary>
        /// <param name="message">The message to send.</param>
        /// <param name="userState">
        /// A User-defined object that is passed to the method invoked when the asynchronous operation completes.
        /// </param>
        public void SendMessage(Message message, object userState)
        {
            if (message == null)
            {
                throw ExceptionHelper.CreateArgumentNullException("message");
            }

            message.Sender = this.user.Address;
            this.user.MessagesFactory.Add(message);

            this.entity.Invoke("SendMessage", message.Entity, userState);
        }

        /// <summary>
        /// Closes the conversation.
        /// </summary>
        public void Close()
        {
            this.entity.Invoke("Close");
        }

        /// <summary>
        /// Determines whether the provided message type is supported by this conversation.
        /// </summary>
        /// <param name="messageType">The message type.</param>
        /// <returns><c>true</c> if the message type is supported; <c>false</c> otherwise.</returns>
        public bool SupportsMessageType(MessageType messageType)
        {
            return (bool)this.entity.Invoke("SupportsMessageType", messageType);
        }

        /// <summary>
        /// Determines whether the provided application message is supported by this conversation.
        /// </summary>
        /// <param name="id">The ID of the application message.</param>
        /// <returns><c>true</c> if supported; <c>false</c> otherwise.</returns>
        public bool SupportsApplicationMessageType(string id)
        {
            if (String.IsNullOrEmpty(id))
            {
                throw ExceptionHelper.CreateArgumentNullException("id");
            }

            return (bool)this.entity.Invoke("SupportsApplicationMessageType", id);
        }

        /// <summary>
        /// Determines whether the provided text message is valid within this conversation.
        /// </summary>
        /// <param name="textMessage">The text message to validate.</param>
        /// <returns><c>true</c> if the text message is valid; <c>false</c> otherwise.</returns>
        public bool IsTextMessageValid(string textMessage)
        {
            if (textMessage == null)
            {
                throw ExceptionHelper.CreateArgumentNullException("textMessage");
            }

            return (bool)this.entity.Invoke("IsTextMessageValid", textMessage);
        }

        /// <summary>
        /// Occurs when <see cref="InviteAddress"/> has completed.
        /// </summary>
        public event InviteAddressCompletedEventHandler InviteAddressCompleted;

        /// <summary>
        /// Occurs when <see cref="SendMessage"/> has failed.
        /// </summary>
        public event SendMessageFailedEventHandler SendMessageFailed;

        /// <summary>
        /// Occurs when a message has been received.
        /// </summary>
        public event MessageReceivedEventHandler MessageReceived;

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        private void OnMessageReceived(object sender, MessengerEventArgs e)
        {
            this.OnMessageReceived(
                new MessageReceivedEventArgs(
                    this.user.MessagesFactory.FindOrCreate(e.Entity.GetProperty("Message"))));
        }

        /// <summary>
        /// Raises the <see cref="MessageReceived"/> event.
        /// </summary>
        /// <param name="e">
        /// A <see cref="MessageReceivedEventArgs"/> object containing event data.
        /// </param>
        internal protected virtual void OnMessageReceived(MessageReceivedEventArgs e)
        {
            if (this.MessageReceived != null)
            {
                this.MessageReceived(this, e);
            }
        }

        private void OnInviteAddressCompleted(object sender, MessengerEventArgs e)
        {
            this.OnInviteAddressCompleted(
                new InviteAddressCompletedEventArgs(
                    e.Entity.GetEnumProperty<InviteAddressResultCode>("ResultCode"),
                    this.user.IMAddressFactory.FindOrCreate(e.Entity.GetProperty("IMAddress")),
                    e.Entity.GetProperty("UserState")));
        }

        /// <summary>
        /// Raises the <see cref="InviteAddressCompleted"/> event.
        /// </summary>
        /// <param name="e">
        /// An <see cref="InviteAddressCompletedEventArgs"/> object containing event data.
        /// </param>
        internal protected virtual void OnInviteAddressCompleted(InviteAddressCompletedEventArgs e)
        {
            if (this.InviteAddressCompleted != null)
            {
                this.InviteAddressCompleted(this, e);
            }
        }

        private void OnSendMessageFailed(object sender, MessengerEventArgs e)
        {
            this.OnSendMessageFailed(
                new SendMessageFailedEventArgs(
                    e.Entity.GetEnumProperty<SendMessageResultCode>("ResultCode"),
                    this.user.MessagesFactory.FindOrCreate(e.Entity.GetProperty("Message")),
                    e.Entity.GetProperty("UserState")));
        }

        /// <summary>
        /// Raises the <see cref="SendMessageFailed"/> event.
        /// </summary>
        /// <param name="e">
        /// An <see cref="SendMessageFailedEventArgs"/> object containing event data.
        /// </param>
        internal protected virtual void OnSendMessageFailed(SendMessageFailedEventArgs e)
        {
            if (this.SendMessageFailed != null)
            {
                this.SendMessageFailed(this, e);
            }
        }

        private void OnPropertyChanged(object sender, MessengerEventArgs e)
        {
            this.OnPropertyChanged(
                new PropertyChangedEventArgs(
                    e.Entity.GetProperty<string>("PropertyChanged")));
        }

        /// <summary>
        /// Raises the <see cref="PropertyChanged"/> event.
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, e);
            }
        }

        #region IDisposable Members

        /// <summary>
        /// Disposes the conversation.
        /// </summary>
        public void Dispose()
        {
            this.entity.Invoke("Dispose");
        }

        #endregion
    }
}
